﻿using System;
using System.Collections;
using System.Configuration;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace ScreenShark.Configuration.Settings
{
    public abstract class CustomFolderSettingsProvider : SettingsProvider
    {
        protected CustomFolderSettingsProvider()
        {
            this.UseCompositeXml = true;
        }

        public override string ApplicationName { get; set; }

        public bool UseCompositeXml { get; set; }

        public override void Initialize(string name, NameValueCollection config)
        {
            if (String.IsNullOrEmpty(name))
            {
                name = this.GetType().Name;
            }
            base.Initialize(name, config);
        }

        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            string settingsRoot = GetSettingsRoot(context);

            var propertyValueCollection = new SettingsPropertyValueCollection();

            foreach (SettingsProperty setting in collection)
            {
                var propertyValue = new SettingsPropertyValue(setting);
                propertyValue.IsDirty = false;
                propertyValue.SerializedValue = this.GetValue(setting, settingsRoot);
                
                propertyValueCollection.Add(propertyValue);
            }

            return propertyValueCollection;
        }

        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            string settingsRoot = GetSettingsRoot(context);

            foreach (SettingsPropertyValue propertyValue in collection)
            {
                this.SetValue(propertyValue, settingsRoot);
            }

            try
            {
                if (!Directory.Exists(this.SettingsPath))
                {
                    Directory.CreateDirectory(this.SettingsPath);
                }
                this.SettingsXml.Save(Path.Combine(this.SettingsPath, this.SettingsFileName));
            }
            catch (Exception e)
            {
                // Log and suppress exception
                string msg = String.Format("Cannot save file: {0} - {1}", e.GetType().Name, e.Message);
                Trace.WriteLine(msg);
            }
        }


        protected abstract string SettingsPath
        {
            get;
        }

        protected virtual string SettingsFileName
        {
            get { return this.ApplicationName + ".settings"; }
        }

        private static string GetSettingsRoot(SettingsContext context)
        {
            if (context.ContainsKey("GroupName"))
            {
                return (string)context["GroupName"];
            }
            return "Settings";
        }

        private void SerializeValue(SettingsPropertyValue propertyValue, XmlElement settingsNode)
        {
            if (!this.SerializeAsInnerXml(propertyValue, settingsNode))
            {
                settingsNode.RemoveAttribute("composite");
                if (propertyValue.PropertyValue == null)
                {
                    settingsNode.InnerText = String.Empty;
                }
                else
                {
                    settingsNode.InnerText = propertyValue.SerializedValue.ToString();
                }
            }
        }

        private bool SerializeAsInnerXml(SettingsPropertyValue propertyValue, XmlElement settingsNode)
        {
            if (!this.UseCompositeXml)
            {
                return false;
            }

            object value = propertyValue.PropertyValue;
            if (value == null)
            {
                return false;
            }

            Type valueType = value.GetType();
            var xmlRootAttributes = (XmlRootAttribute[])valueType.GetCustomAttributes(typeof(XmlRootAttribute), false);
            if (xmlRootAttributes == null || xmlRootAttributes.Length == 0)
            {
                return false;
            }

            var serializer = new XmlSerializer(valueType);
            using (var ms = new MemoryStream())
            {
                serializer.Serialize(ms, value);
                ms.Position = 0;

                var tmpDoc = new XmlDocument();
                tmpDoc.Load(ms);

                var tmpNode = (XmlElement)this.SettingsXml.ImportNode(tmpDoc.DocumentElement, true);
                tmpNode.RemoveAttribute("xmlns:xsi");
                tmpNode.RemoveAttribute("xmlns:xsd");

                settingsNode.InnerXml = String.Empty;
                settingsNode.AppendChild(tmpNode);
                settingsNode.SetAttribute("composite", "true");
            }

            return true;
        }

        private void SetValue(SettingsPropertyValue propertyValue, string settingsRoot)
        {
            if (!IsUserScoped(propertyValue.Property))
            {
                return;
            }

            XmlElement settingsNode;
            try
            {
                settingsNode = (XmlElement)this.SettingsXml.SelectSingleNode(ConfigRoot + "/" + settingsRoot + "/" + propertyValue.Name);
            }
            catch (Exception)
            {
                settingsNode = null;
            }

            if (settingsNode != null)
            {
                this.SerializeValue(propertyValue, settingsNode);
            }
            else
            {
                settingsNode = this.SettingsXml.CreateElement(propertyValue.Name);
                this.SerializeValue(propertyValue, settingsNode);

                var configRootNode = this.SettingsXml.SelectSingleNode(ConfigRoot);
                var settingsRootNode = configRootNode.SelectSingleNode(settingsRoot);
                if (settingsRootNode == null)
                {
                    settingsRootNode = g_settingsXml.CreateNode(XmlNodeType.Element, settingsRoot, "");
                    configRootNode.AppendChild(settingsRootNode);
                }
                settingsRootNode.AppendChild(settingsNode);
            }
        }

        private object GetValue(SettingsProperty setting, string settingsRoot)
        {
            string configPath = GetConfigPath(settingsRoot, setting);
            var settingsNode = (XmlElement)this.SettingsXml.SelectSingleNode(configPath);
            if (settingsNode != null)
            {
                if (settingsNode.GetAttribute("composite") == "true")
                {
                    return settingsNode.InnerXml;
                }
                else
                {
                    return settingsNode.InnerText;
                }
            }
            else if (setting.DefaultValue != null)
            {
                return setting.DefaultValue.ToString();
            }
            else
            {
                return string.Empty;
            }
        }

        private static string GetConfigPath(string settingsRoot, SettingsProperty setting)
        {
            return ConfigRoot + "/" + settingsRoot + "/" + setting.Name;
        }

        private static bool IsUserScoped(SettingsProperty prop)
        {
            foreach (DictionaryEntry d in prop.Attributes)
            {
                if (d.Value.GetType() == typeof(UserScopedSettingAttribute))
                {
                    return true;
                }
            }
            return false;
        }

        private XmlDocument SettingsXml
        {
            get
            {
                if (g_settingsXml == null)
                {
                    g_settingsXml = new XmlDocument();

                    try
                    {
                        g_settingsXml.Load(Path.Combine(this.SettingsPath, this.SettingsFileName));
                    }
                    catch (Exception)
                    {
                        var xmlDeclaration = g_settingsXml.CreateXmlDeclaration("1.0", "utf-8", String.Empty);
                        g_settingsXml.AppendChild(xmlDeclaration);
                        var nodeRoot = g_settingsXml.CreateNode(XmlNodeType.Element, ConfigRoot, "");
                        g_settingsXml.AppendChild(nodeRoot);
                    }
                }

                return g_settingsXml;
            }
        }


        #region Private Data Members

        private static XmlDocument g_settingsXml;
        private const string ConfigRoot = "Configuration";

        #endregion
    }

}
